package com.ats.audiotoscore;

import java.io.*;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

import com.ats.audiotoscore.AudioCaptureCallback;
import com.ats.audiotoscore.Utils;
import com.ats.audiotoscore.WindowFunction;
import com.ats.audiotoscore.WavFile;

public class AudioCapture extends Thread {
	private static final String TAG = "AudioCapture";
	private String waveFilename = null;
	private AudioCaptureCallback audioCaptureCB;
	private AudioRecord aRecord = null;
	private WavFile wav = null;
	private long samplesCount = 0;
	private long samplesStart = 0;
	private int bufferSize = 0;
	private int sampleRate = 0;
	private short buffer[] = null;
	private double samples[] = null;
	private double windowBuffer[] = null;
	private int wavdata[][];
	private boolean running = true;
	private long msTime = 0;

	public AudioCapture(AudioCaptureCallback cb, String filename, int _sampleRate) {
		sampleRate = _sampleRate;
		audioCaptureCB = cb;
		waveFilename = filename;
		start();
	}

	public void terminate() {
		running = false;
	}

	@Override
	public void run() {
		setPriority(MAX_PRIORITY);
		if (waveFilename == null) {
			startRecording();
		} else {
			openWavefile(waveFilename);
		}

		while (running) {
			updateRecording();
		}

		stopRecording();
	}

	public void applyWindow(short[] data, double[] output) {
		for (int i=0; i <data.length; i++) {
			output[i] = data[i] * windowBuffer[i];
		}
	}

    private void updateSamples() {
		audioCaptureCB.OnSamplesUpdate(samples, sampleRate, getMsTime());
		samplesCount+= samples.length;
	}

    public int getSampleRate() {
    	return sampleRate;
    }

    public long getSamplesPerSecond() {
    	long secs = ((System.currentTimeMillis() - samplesStart) / 1000) + 1;
    	if (secs == 0)
    		return 0;
    	return samplesCount / secs;
    }

    private void openWavefile(String filename) {
		try
		{
			wav = WavFile.openWavFile(new File(filename));
			sampleRate = (int)wav.getSampleRate();
			initBuffers(1024, (int)wav.getSampleRate());
			wavdata = new int[wav.getNumChannels()][bufferSize];
		}
		catch (Exception e)
		{
			System.err.println(e);
		}
    }

	private void updateRecording() {
		if (wav != null) {
			try
			{
				if (wav.readFrames(wavdata, samples.length) != 0) {
					for (int i=0; i<samples.length; i++) {
						samples[i] = wavdata[0][i];
					}
					updateSamples();
					msTime+= (samples.length * 1000) / wav.getSampleRate();
				} else {
					wav.close();
					wav = null;
				}
			}
			catch (Exception e)
			{
				System.err.println(e);
			}
		} else if (aRecord != null) {
			int size = aRecord.read(buffer, 0, bufferSize);
			if (size > 0) {
				applyWindow(buffer, samples);
				updateSamples();
			}
		} else {
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
				System.err.println(e);
			}
		}
	}

	private AudioRecord initRecorderParameters(int[] sampleRates) {
		for (int i = 0; i < sampleRates.length; ++i){
			try {
				int tmpBufferSize = AudioRecord.getMinBufferSize(sampleRates[i], AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

	            // Test the minimum allowed buffer size with this configuration on this device.
	            if (tmpBufferSize != AudioRecord.ERROR_BAD_VALUE){
	                // Seems like we have ourself the optimum AudioRecord parameter for this device.
	                AudioRecord tmpRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRates[i], AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, tmpBufferSize);
	                // Test if an AudioRecord instance can be initialized with the given parameters.
	                if (tmpRecorder.getState() == AudioRecord.STATE_INITIALIZED){
	                    initBuffers(tmpBufferSize/2, sampleRates[i]);
	                    return tmpRecorder;
	                }
	            } else {
	                Log.w(TAG, "Incorrect buffer size. Continue sweeping Sampling Rate...");
	            }
	        } catch (IllegalArgumentException e) {
	            Log.e(TAG, "The "+sampleRates[i]+"Hz Sampling Rate is not supported on this device");
	        }
	    }

		return null;
	}

	public void startRecording() {
		if (aRecord != null) {
			return;
		}

		if (sampleRate != 0) {
			int[] samples = { sampleRate } ;
			aRecord = initRecorderParameters(samples);
			if (aRecord != null) {
				aRecord.startRecording();
				return;
			}
		}

		if (aRecord == null) {
			int[] samples441 = { 44100, 22050, 11025, 8000 };
			int[] samples220 = { 22050, 11025, 8000 };
			int[] samples110 = { 11025, 8000 };
			int[] samples80 = { 8000 };

			int speed = Utils.GetCPUSpeed();
			if (speed == 0 || speed >= 1500000) {
				aRecord = initRecorderParameters(samples441);
			} else {
				if (speed >= 1200000) {
					aRecord = initRecorderParameters(samples220);
				} else if (speed >= 900000) {
					aRecord = initRecorderParameters(samples110);
				} else {
					aRecord = initRecorderParameters(samples80);
				}
			}
		}

		if (aRecord == null)
			return;

		aRecord.startRecording();
	}

	private void initBuffers(int size, int sRate) {
        bufferSize = size;
        sampleRate = sRate;
        buffer = new short[bufferSize];
        samples = new double[bufferSize];

        WindowFunction wf = new WindowFunction(WindowFunction.HANNING);
		windowBuffer = wf.generate(bufferSize);

		samplesStart = System.currentTimeMillis();
	}

	public void stopRecording() {
		if (aRecord != null) {
			aRecord.stop();
			aRecord.release();
			aRecord = null;
		}

		if (wav != null) {
			try
			{
				wav.close();
			}
			catch (Exception e)
			{
				System.err.println(e);
			}
			wav = null;
		}
	}

	public boolean isRecording() {
		return (aRecord != null) || (wav != null);
	}

	public long getMsTime() {
		if (waveFilename == null) {
			return System.currentTimeMillis() - samplesStart;
		} else {
			return msTime;
		}
	}
}
